package com.airbnb.aerosolve.core.function;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import java.util.Arrays;
import java.util.List;
@Slf4j
public class FunctionUtil {
public static float[] fitPolynomial(float[] data) {
int numCoeff = 6;
int iterations = numCoeff * 4;
float[] initial = new float[numCoeff];
float[] initialStep = new float[numCoeff];
Arrays.fill(initialStep, 1.0f);
return optimize(1.0 / 512.0, iterations, initial, initialStep,
new ImmutablePair<Float, Float>(-10.0f, 10.0f), data);
}
public static float evaluatePolynomial(float[] coeff, float[] data, boolean overwrite) {
int len = data.length;
float err = 0;
long count = 0;
for (int i = 0; i < len; i++) {
float t = (float) i / (len - 1);
float tinv = 1 - t;
float diracStart = (i == 0) ? coeff[0] : 0;
float diracEnd = (i == len - 1) ? coeff[1] : 0;
double eval = coeff[2] * tinv * tinv * tinv +
coeff[3] * 3.0 * tinv * tinv * t +
coeff[4] * 3.0 * tinv * t * t +
coeff[5] * t * t * t +
diracStart +
diracEnd;
if (data[i] != 0.0) {
err += Math.abs(eval - data[i]);
count++;
}
if (overwrite) {
data[i] = (float) eval;
}
}
return err / count;
}
// CyclicCoordinateDescent
public static float[] optimize(double tolerance, int iterations,
float[] initial, float[] initialStep,
Pair<Float, Float> bounds, float[] data) {
float[] best = initial;
float bestF = evaluatePolynomial(best, data, false);
int maxDim = initial.length;
for (int i = 0; i < iterations; ++i) {
for (int dim = 0; dim < maxDim; ++dim) {
float step = initialStep[dim];
while (step > tolerance) {
float[] left = best.clone();
left[dim] = Math.max(bounds.getLeft(), best[dim] - step);
float leftF = evaluatePolynomial(left, data, false);
float[] right = best.clone();
right[dim] = Math.min(bounds.getRight(), best[dim] + step);
float rightF = evaluatePolynomial(right, data, false);
if (leftF < bestF) {
best = left;
bestF = leftF;
}
if (rightF < bestF) {
best = right;
bestF = rightF;
}
step *= 0.5;
}
}
}
return best;
}
public static float[] toFloat(List<Double> list) {
float[] result = new float[list.size()];
for (int i = 0; i < result.length; i++) {
result[i] = list.get(i).floatValue();
}
return result;
}
/*
* @param tolerance if fitted array's deviation from weights is less than tolerance
* use the fitted, otherwise keep original weights.
* @param weights the curve you want to smooth
* @return double errAndCoeff in the weights
*/
public static double smooth(double tolerance, boolean toleranceIsPercentage, float[] weights) {
// TODO use apache math's PolynomialCurveFitter
float[] best = FunctionUtil.fitPolynomial(weights);
double errAndCoeff = FunctionUtil.evaluatePolynomial(best, weights, false);
if (toleranceIsPercentage) {
double absMean = getAbsMean(weights);
return smoothInternal(errAndCoeff, tolerance * absMean, best, weights) / absMean;
} else {
return smoothInternal(errAndCoeff, tolerance, best, weights);
}
}
private static double smoothInternal(
double errAndCoeff, double tolerance, float[] best, float[] weights) {
if (errAndCoeff < tolerance) {
FunctionUtil.evaluatePolynomial(best, weights, true);
}
return errAndCoeff;
}
public static double getAbsMean(float[] weights) {
double sum = 0;
for (float f : weights) {
sum += Math.abs(f);
}
return sum / weights.length;
}
}